#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <memory>
#include <atomic>
#include <iostream>
#include <sstream>
using std::cout;
using std::endl;

void newInterface()
{
    cout << "newInterface" << endl;
}

void releaseInterface()
{
    cout << "releaseInterface" << endl;
}

void testInterface()
{
    cout << "testInterface" << endl;
}

typedef void (*funTest)();

struct sTest {
    funTest newi;
    funTest resi;
    funTest tsti;
    sTest() {
        cout << "sTest : new" << (int)this << endl;
    }

    ~sTest() {
        cout << "sTest : delete" << (int)this << endl;
    }
};

void setSTest(sTest *st) {
    st->newi = newInterface;
    st->resi = releaseInterface;
    st->tsti = testInterface;
}

void TestAtomicFlag();
void TestAtomicBool();
void TestAtomicInt();
void TestAtomicEnum();
void TestAtomicTicket();

int main(int argc, char* argv[])
{
    {
        sTest st;
        setSTest(&st);

        auto t1 = std::shared_ptr<sTest>(&st, [](sTest* st){
            st->resi();
        });
    }

    cout << "--------------------" << endl;

    sTest *aaa = nullptr;
    {
        auto t2 = std::shared_ptr<sTest>(new sTest
            , [](sTest* st) {
                setSTest(st);
                st->resi();
                delete st;
            });
        aaa = t2.get();
    }

    //aaa->tsti(); // error,had deleted.
    
    cout << "--------------------" << endl;
    /*{
        std::allocator<sTest> alloc;

        auto t3 = std::shared_ptr<sTest>(
            new sTest
            , []() -> sTest* {
                auto st = new sTest;
                setSTest(st);
                return st;
            }
            , alloc);
    }*/

    cout << "----------TestAtomicFlag----------" << endl;
    TestAtomicFlag();
    cout << "----------TestAtomicBool----------" << endl;
    TestAtomicBool();
    cout << "-----------TestAtomicInt---------" << endl;
    TestAtomicInt();
    cout << "-----------TestAtomicEnum---------" << endl;
    //TestAtomicEnum();
    cout << "-----------TestAtomicTicket---------" << endl;
    TestAtomicTicket();
    cout << "-----------Test shared_ptr---------" << endl;
    {
        std::shared_ptr<sTest> sp1 = nullptr;
        sp1 = std::shared_ptr<sTest>(
              new sTest
            , [](sTest* st){
                delete st;
            }
        );
        setSTest(sp1.get());
        sp1->tsti();

        auto fun1 = [](std::shared_ptr<sTest>& sp) {
            auto funnew = [](){
                cout << "newInterface new" << endl;
            };
            auto fundel = [](){
                cout << "deltInterface new" << endl;
            };
            auto funtest = [](){
                cout << "testInterface new" << endl;
            };
            sp = std::shared_ptr<sTest>(
                new sTest
                , [](sTest* st){
                    delete st;
                }
            );
            
            sp->newi = funnew;
            sp->resi = fundel;
            sp->tsti = funtest;
        };

        fun1(sp1);
        sp1->tsti();
    }
    cout << "-----------Test unique_ptr---------" << endl;
    {
        /*std::unique_ptr<sTest> sp1 = nullptr;
        sp1 = std::unique_ptr<sTest>(
              new sTest
            , [](sTest* st){
                delete st;
            }
        );
        setSTest(sp1.get());
        sp1->tsti();*/
    }

    cout << "-----------Test atomic---------" << endl;
    {
        std::atomic<int> ai1(10);
        auto fun = [](std::atomic<int> &ai){
            ai = 20;
        };
        cout << ai1 << endl;
        fun(ai1);
        cout << ai1 << endl;
    }

    return 0;
}

//------------------------//
// bool
void TestAtomicFlag() {
    std::atomic_flag atmc_flag = ATOMIC_FLAG_INIT;
    std::stringstream stream;
    std::vector<std::thread> ths;

    for (int i=0; i<10; i++)
        ths.push_back(std::thread([&atmc_flag, &stream](int x) {
            while (atmc_flag.test_and_set());
            stream << "thread#" << x << "\n";
            atmc_flag.clear();
        }, i));
    for (int i=0; i<10; i++)
        ths[i].join();
    std::cout << stream.str();
}

//bool
void TestAtomicBool() {
    std::atomic_flag atmc_flag = ATOMIC_FLAG_INIT;
    std::atomic<bool> atmc(false);
    std::stringstream stream;
    std::vector<std::thread> ths;

    for (int i=0; i<10; i++)
        ths.push_back(std::thread([&atmc, &atmc_flag, &stream](int x) {
            while (!atmc);
            for (int i = 0; i < 1000000; i++);
            if (!atmc_flag.test_and_set())
                stream << "atmc_flag: false -> " << x << std::endl;
            else
                stream << "atmc_flag: true -> " << x << std::endl;
        }, i));
    
    atmc = true;

    for (int i=0; i<10; i++)
        ths[i].join();
    std::cout << stream.str();
}

// int
void TestAtomicInt() {
    std::atomic_flag atmc_flag = ATOMIC_FLAG_INIT;
    std::atomic<int> atmc(0);
    std::stringstream stream;

    std::thread th2 = std::thread([&atmc, &atmc_flag, &stream]() {
        while (true) {
            ++atmc;
            while (atmc_flag.test_and_set());
            stream <<"add: " << atmc << std::endl;
            atmc_flag.clear();

            std::chrono::milliseconds dura(1000);
            std::this_thread::sleep_for(dura);
            break;
        }
    });

    std::thread th1 = std::thread([&atmc, &atmc_flag, &stream]() {
        while (true) {
            --atmc;
            while (atmc_flag.test_and_set());
            stream <<"sub: " << atmc << std::endl;
            atmc_flag.clear();

            std::chrono::milliseconds dura(1000);
            std::this_thread::sleep_for(dura);
            break;
        }
    });
    th1.join();
    th2.join();
    std::cout << stream.str();
}

void TestAtomicEnum() {
    enum class eType : uint32_t {
          NUL, INIT, OPEN, RUN
        , PAUSE, CONTINUE, STOP
        , CLOSE, DONE
    };

    std::atomic_flag atmc_flag = ATOMIC_FLAG_INIT;
    //std::atomic<eType> atmc(eType::NUL);
    eType atmc = eType::NUL;
    std::vector<std::thread> ths;

    auto setType = [&atmc, &atmc_flag](eType t) {
        //while(!atmc_flag.test_and_set());
        atmc = t;
        //atmc_flag.clear();
    };

    auto handleMgr = [&setType](eType t, int x) -> bool {
        std::cout << x << " ";
        switch (t) {
            case eType::NUL:{
                std::cout << "NULL: " << std::endl;
            } break;
            case eType::INIT:{
                std::cout << "INIT: " << std::endl;
            } break;
            case eType::OPEN:{
                std::cout << "OPEN: " << std::endl;
            } break;
            case eType::RUN:{
                std::cout << "RUN: " << std::endl;
            } break;
            case eType::PAUSE:{
                std::cout << "PAUSE: " << std::endl;
            } break;
            case eType::CONTINUE:{
                std::cout << "CONTINUE: " << std::endl;
            } break;
            case eType::STOP:{
                std::cout << "STOP: " << std::endl;
            } break;
            case eType::CLOSE:{
                std::cout << "CLOSE: " << std::endl;
            } break;
            case eType::DONE:{
                std::cout << "DONE: " << std::endl;
                return false;
            } break;
            default : {
                std::cout << "error etype." << std::endl;
                return false;
            } break;
        }
        return true;
    };

    for (int i=0; i<10; i++)
        ths.push_back(std::thread([&atmc, &handleMgr](int x) {
            while (handleMgr(atmc, x));
        }, i));

    std::thread td([&setType](int x) {
        std::chrono::milliseconds dura(1000);
        std::this_thread::sleep_for(dura);
        setType(eType::INIT);
        std::this_thread::sleep_for(dura);
        setType(eType::OPEN);
        std::this_thread::sleep_for(dura);
        setType(eType::RUN);
        std::this_thread::sleep_for(dura);
        setType(eType::PAUSE);
        std::this_thread::sleep_for(dura);
        setType(eType::CONTINUE);
        std::this_thread::sleep_for(dura);
        setType(eType::STOP);
        std::this_thread::sleep_for(dura);
        setType(eType::CLOSE);
        std::this_thread::sleep_for(dura);
        setType(eType::DONE);
    }, 10);

    for (int i=0; i<10; i++)
        ths[i].join();
    td.join();
}

void TestAtomicTicket() {
    //std::atomic<int> tickets(2000);
    long tickets = 2000;
    std::vector<std::thread> ths;
    std::mutex mtx;

    auto getTicket = [&tickets]() -> long {
        return tickets--;
        //return tickets.exchange(tickets - 1);
    };

    int count = 0;

    for (int i = 0; i < 100; i++)
        ths.push_back(std::thread([&getTicket, &mtx, &count](int x) {
            while (true) {
                long id = getTicket();
                if (id <= 0) break;
                
                {
                    std::lock_guard<std::mutex> lock(mtx);
                    count++;
                    std::cout << "thread#" << x << "-> "<< id << ", " << count << "\n";
                }
            }
        }, i));
    for (int i = 0; i < 100; i++)
        ths[i].join();
}